<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>批量爬虫查询-Websocket版-结果页</title>
    <link href="/static/layui/css/layui.css" rel="stylesheet">
    <script src="/static/layui/layui.js"></script>
    <script src="/static/js/axios.min.js"></script>
    <script src="/static/js/FileSaver.min.js"></script>
    <script src="/static/js/xlsx.full.min.js"></script>
    <link rel="shortcut icon" href="/static/groupcheck.ico"/>
    <style>
        /* 返回顶部按钮样式 */
        .back-to-top {
            position: fixed;
            bottom: 50px;
            right: 30px;
            width: 50px;
            height: 50px;
            background-color: #1E9FFF;
            color: white;
            border-radius: 50%;
            text-align: center;
            line-height: 50px;
            font-size: 20px;
            cursor: pointer;
            z-index: 9999;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
            transition: all 0.3s ease;
            display: none;
            border: none;
        }

        .back-to-top:hover {
            background-color: #009688;
            transform: translateY(-3px);
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
        }

        .back-to-top:active {
            transform: translateY(0);
        }
    </style>
</head>
<body>
<div class="layui-layout layui-layout-admin">
    <div class="layui-side layui-bg-black">
        <div class="layui-side-scroll">
            <ul class="layui-nav layui-nav-tree layui-nav-side">
                <li class="layui-nav-item"><a href="/groupcheck4/step1">组号检查</a></li>
                <li class="layui-nav-item layui-this"><a href="<?php echo $back_url;?>">爬虫检查</a></li>
            </ul>
        </div>
    </div>

    <div class="layui-body" style="top:15px;">
        <div style="padding-left: 16px;padding-right: 16px;">
            <form class="layui-form" action="">
                <div class="layui-form-item layui-form-text">
                    <label class="layui-form-label">处理结果</label>
                    <div class="layui-input-block">
                        <table class="layui-table">
                            <colgroup><col width="80"><col width="300"><col width="120"><col width=""></colgroup>
                            <thead><tr><th>序号</th><th>域名</th><th>结果</th><th id="copy-btn">说明-点击复制</th></tr></thead>
                            <tbody>
                            <?php if(!empty($domains)){foreach ($domains as $item){?>
                                <tr>
                                    <td><?php echo $item['index'];?></td>
                                    <td id="domain-<?php echo $item['index'];?>"><?php echo $item['domain'];?></td>
                                    <td id="ret-<?php echo $item['index'];?>"><?php echo $item['ret'];?></td>
                                    <td id="btn-<?php echo $item['index'];?>"><?php echo $item['body'];?></td>
                                </tr>
                            <?php }} ?>
                            </tbody>
                        </table>
                        <p>🔔🔔总计🔔🔔  共<?php echo $domain_count;?>笔，其中「成功:<span id="num_success">0</span>」「失败:<span id="num_failure">0</span>」</p>
                    </div>
                </div>
                <div class="layui-form-item">
                    <div class="layui-input-block">
                        <a href="<?php echo $back_url;?>" class="layui-btn"><i class="layui-icon layui-icon-backspace"></i>返回上一页 <<</a>
                        <a class="layui-btn layui-btn-primary layui-border" href="javascript:void(0)" id="exportExcelBtn"><i class="layui-icon layui-icon-down"></i>导出到Excel</a>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>

<!-- 返回顶部按钮 -->
<button class="back-to-top" id="backToTopBtn" title="返回顶部">
    ↑
</button>

<script>
    let socket;
    let url = "<?php echo $websocket_url;?>";
    let reconnectInterval = 2000;

    // 跟踪请求状态
    const pendingRequests = new Set();
    const responseHandlers = new Map();
    const requestTimeouts = new Map();
    const cellStatus = new Map(); // 跟踪每个单元格的状态: 'idle', 'pending', 'completed', 'timeout'
    const requestTimestamps = new Map(); // 记录每个请求的发起时间
    const requestSequences = new Map(); // 记录每个单元格的请求序列号

    // 文本消息处理函数
    function handleTextMessage(text) {
        if (text.includes("websocket-Opened")) {
            console.log("WebSocket 连接成功！");
        } else if (text.startsWith("Error:")) {
            console.error("服务器错误:", text);
        }
    }

    function connect() {
        console.log("正在建立 WebSocket 连接...");

        // 先清除所有状态
        pendingRequests.clear();
        responseHandlers.clear();
        cellStatus.clear();
        requestTimestamps.clear();
        requestSequences.clear();
        requestTimeouts.forEach((timeoutId) => {
            clearTimeout(timeoutId);
        });
        requestTimeouts.clear();

        socket = new WebSocket(url);

        socket.onopen = function (event) {
            console.log("WebSocket 连接已建立");
            // 连接重建后重新尝试所有pending的请求
            retryPendingRequests();
        };

        socket.onmessage = function (event) {
            console.log("收到消息:", event.data);
            try {
                const response = JSON.parse(event.data);
                const handlerKey = `${response.domain}_${response.index}`;
                const handler = responseHandlers.get(handlerKey);

                if (handler) {
                    const { handler: callback, sequence } = handler;

                    // 检查是否为最新的请求（防止旧响应覆盖新结果）
                    const cellKey = `cell_${response.index}`;
                    const currentSequence = requestSequences.get(cellKey) || 0;

                    if (sequence < currentSequence) {
                        console.log(`忽略旧的响应，当前序列: ${currentSequence}, 响应序列: ${sequence}`);
                        return; // 忽略旧的响应
                    }

                    // 清除超时定时器
                    const timeoutId = requestTimeouts.get(handlerKey);
                    if (timeoutId) {
                        clearTimeout(timeoutId);
                        requestTimeouts.delete(handlerKey);
                    }

                    callback(response);
                    responseHandlers.delete(handlerKey);
                    pendingRequests.delete(handlerKey);

                    // 更新单元格状态为已完成
                    cellStatus.set(cellKey, 'completed');

                    // 移除时间戳记录
                    requestTimestamps.delete(handlerKey);
                }
            } catch (error) {
                handleTextMessage(event.data);
            }
        };

        socket.onerror = function (error) {
            console.error("WebSocket 出错:", error);
        };

        socket.onclose = function (event) {
            console.log("WebSocket 连接关闭，代码:", event.code, "原因:", event.reason);

            // 清除所有超时定时器
            requestTimeouts.forEach((timeoutId) => {
                clearTimeout(timeoutId);
            });
            requestTimeouts.clear();

            // 清空所有状态但保留pending信息用于重试
            responseHandlers.clear();

            // 尝试重新连接
            setTimeout(connect, reconnectInterval);
        };
    }

    // 重试所有pending的请求
    function retryPendingRequests() {
        console.log("准备重试pending请求");

        // 重置所有单元格状态为 idle，允许重新发送请求
        cellStatus.forEach((status, key) => {
            if (status === 'pending' || status === 'timeout') {
                cellStatus.set(key, 'idle');
            }
        });

        // 先清除所有pending状态
        pendingRequests.clear();
        responseHandlers.clear();
        requestTimestamps.clear();
        requestTimeouts.forEach((timeoutId) => {
            clearTimeout(timeoutId);
        });
        requestTimeouts.clear();

        // 延迟一下再重新检查，避免瞬间大量请求
        setTimeout(() => {
            checkTableStatus();
        }, 1000);
    }

    connect();

    function sendMessage(message, sequence) {
        const { domain, index } = message;
        const handlerKey = `${domain}_${index}`;
        const cellKey = `cell_${index}`;

        if (socket.readyState === WebSocket.OPEN) {
            return new Promise((resolve, reject) => {
                // 标记该请求正在进行中
                pendingRequests.add(handlerKey);

                // 记录请求发起时间
                requestTimestamps.set(handlerKey, Date.now());

                // 注册响应处理函数（包含序列号）
                responseHandlers.set(handlerKey, {
                    handler: (response) => {
                        resolve(response);
                    },
                    sequence: sequence
                });

                // 设置60秒超时
                const timeoutId = setTimeout(() => {
                    console.warn(`请求超时: ${handlerKey}, 序列号: ${sequence}`);

                    // 检查是否为最新的请求
                    const currentSequence = requestSequences.get(cellKey) || 0;
                    if (sequence < currentSequence) {
                        console.log(`忽略旧的超时，当前序列: ${currentSequence}, 超时序列: ${sequence}`);
                        return;
                    }

                    // 更新单元格状态为超时
                    cellStatus.set(cellKey, 'timeout');

                    // 显示超时信息
                    const retTd = document.getElementById("ret-" + index);
                    const btnTd = document.getElementById("btn-" + index);

                    if (retTd) {
                        retTd.textContent = '超出60秒';
                        retTd.style.color = 'orange';
                        retTd.style.fontWeight = 'bold';
                    }
                    if (btnTd) {
                        btnTd.textContent = '请等待更新';
                    }

                    // 清理状态
                    pendingRequests.delete(handlerKey);
                    responseHandlers.delete(handlerKey);
                    requestTimeouts.delete(handlerKey);
                    requestTimestamps.delete(handlerKey);

                    reject(new Error('请求超时'));
                }, 60000);

                requestTimeouts.set(handlerKey, timeoutId);

                // 发送消息
                try {
                    console.log("发送消息:", message, "序列号:", sequence);
                    socket.send(JSON.stringify(message));
                } catch (error) {
                    console.error("发送消息失败:", error);
                    // 清理状态
                    pendingRequests.delete(handlerKey);
                    responseHandlers.delete(handlerKey);
                    const timeoutId = requestTimeouts.get(handlerKey);
                    if (timeoutId) {
                        clearTimeout(timeoutId);
                        requestTimeouts.delete(handlerKey);
                    }
                    requestTimestamps.delete(handlerKey);
                    reject(error);
                }
            });
        } else {
            console.warn("WebSocket 连接未就绪，当前状态:", socket.readyState);
            return Promise.reject('WebSocket 连接未就绪');
        }
    }

    // 定期检查表格状态
    let checkInterval = setInterval(checkTableStatus, 3000);

    function checkTableStatus() {
        if (socket.readyState !== WebSocket.OPEN) {
            console.log("WebSocket未连接，暂停检查");
            return;
        }

        const tds = document.querySelectorAll('td');
        let num_success = 0, num_failure = 0, num_pending = 0, num_timeout = 0;

        tds.forEach(td => {
            const id = td.id;
            if (id && id.startsWith('ret-')) {
                const index = id.match(/\d+/)[0];
                const domainTd = document.getElementById("domain-" + index);
                if (!domainTd) {
                    console.warn(`未找到 domain-${index} 元素`);
                    return;
                }

                const domain = domainTd.textContent.trim();
                const handlerKey = `${domain}_${index}`;
                const cellKey = `cell_${index}`;

                // 初始化单元格状态和序列号
                if (!cellStatus.has(cellKey)) {
                    cellStatus.set(cellKey, 'idle');
                }
                if (!requestSequences.has(cellKey)) {
                    requestSequences.set(cellKey, 0);
                }

                const currentStatus = cellStatus.get(cellKey);
                const currentContent = td.textContent;
                let currentSequence = requestSequences.get(cellKey);

                // 检查是否已经超时（避免重复显示超时信息）
                if (currentStatus === 'pending') {
                    const requestTime = requestTimestamps.get(handlerKey);
                    if (requestTime && (Date.now() - requestTime) > 60000) {
                        // 检查是否为最新的请求
                        const handlerInfo = responseHandlers.get(handlerKey);
                        if (handlerInfo && handlerInfo.sequence < currentSequence) {
                            console.log(`忽略旧的超时检查，当前序列: ${currentSequence}`);
                            return;
                        }

                        cellStatus.set(cellKey, 'timeout');
                        td.textContent = '超出60秒';
                        td.style.color = 'orange';
                        td.style.fontWeight = 'bold';

                        const btnTd = document.getElementById("btn-" + index);
                        if (btnTd) {
                            btnTd.textContent = '请等待更新';
                        }

                        // 清理状态
                        pendingRequests.delete(handlerKey);
                        responseHandlers.delete(handlerKey);
                        const timeoutId = requestTimeouts.get(handlerKey);
                        if (timeoutId) {
                            clearTimeout(timeoutId);
                            requestTimeouts.delete(handlerKey);
                        }
                        requestTimestamps.delete(handlerKey);
                    }
                }

                // 如果单元格需要查询且状态允许发送请求
                if ((currentContent === '' || currentContent === '排队中' || currentContent === '超出60秒' || currentContent === '请等待更新' || currentContent === '并发过高请稍后查') &&
                    (currentStatus === 'idle' || currentStatus === 'timeout')) {

                    if (!pendingRequests.has(handlerKey)) {
                        // 递增序列号，确保每次请求都有唯一的序列号
                        currentSequence++;
                        requestSequences.set(cellKey, currentSequence);
                        cellStatus.set(cellKey, 'pending');

                        sendMessage({
                            flag: 'spider_check',
                            domain: domain,
                            index: index
                        }, currentSequence)
                            .then(response => {
                                updateTableCell(index, response);
                            })
                            .catch(error => {
                                console.log('请求异常:', error.message);
                                if (error.message !== '请求超时') {
                                    // 非超时错误，重置状态允许重试
                                    pendingRequests.delete(handlerKey);
                                    cellStatus.set(cellKey, 'idle');
                                    requestTimestamps.delete(handlerKey);
                                }
                            });
                    }
                }

                // 统计状态
                if (currentContent === '成功') {
                    num_success++;
                } else if (currentContent === '失败') {
                    num_failure++;
                } else if (currentContent === '超出60秒') {
                    num_timeout++;
                } else if (currentStatus === 'pending') {
                    num_pending++;
                }
            }
        });

        updateCounters(num_success, num_failure, num_pending, num_timeout);
    }

    function updateTableCell(index, response) {
        const retTd = document.getElementById("ret-" + index);
        const btnTd = document.getElementById("btn-" + index);

        if (!retTd || !btnTd) {
            console.warn(`未找到 ret-${index} 或 btn-${index} 元素`);
            return;
        }

        if (response.ret === 'success') {
            retTd.textContent = '成功';
            retTd.style.color = 'green';
            retTd.style.fontWeight = 'bold';
            btnTd.innerHTML = response.body || '完成';
        } else if (response.ret === 'failure') {
            retTd.textContent = '失败';
            retTd.style.color = 'red';
            btnTd.textContent = response.body || '失败';
        } else if (response.ret === 'pending') {
            retTd.textContent = '排队中';
            retTd.style.color = 'blue';
        } else if (response.ret === 'gening') {
            retTd.textContent = '执行中...';
            retTd.style.color = 'orange';
            btnTd.textContent = '执行中请等待';
        }
    }

    function updateCounters(success, failure, pending, timeout = 0) {
        const spanSuccess = document.getElementById('num_success');
        const spanFailure = document.getElementById('num_failure');
        const spanPending = document.getElementById('num_pending');
        const spanTimeout = document.getElementById('num_timeout');

        if (spanSuccess) spanSuccess.innerText = success;
        if (spanFailure) spanFailure.innerText = failure;
        if (spanPending) spanPending.innerText = pending;
        if (spanTimeout) spanTimeout.innerText = timeout;
    }

    // 导出到Excel
    document.getElementById('exportExcelBtn')?.addEventListener('click', function(e) {
        e.preventDefault();
        const table = document.querySelector('.layui-table');
        if (table) {
            const workbook = XLSX.utils.table_to_book(table);
            const now = new Date();
            const timestampSeconds = Math.floor(now.getTime() / 1000);
            XLSX.writeFile(workbook, '爬虫检查-'+timestampSeconds+'.xlsx');
        }
    });

    // 返回顶部功能
    document.addEventListener('DOMContentLoaded', function() {
        const backToTopBtn = document.getElementById('backToTopBtn');

        // 监听滚动事件，显示/隐藏按钮
        window.addEventListener('scroll', function() {
            if (window.pageYOffset > 300) {
                backToTopBtn.style.display = 'block';
            } else {
                backToTopBtn.style.display = 'none';
            }
        });

        // 点击返回顶部
        backToTopBtn.addEventListener('click', function() {
            scrollToTop();
        });

        // 平滑滚动到顶部
        function scrollToTop() {
            const scrollDuration = 500; // 滚动持续时间（毫秒）
            const scrollStep = -window.scrollY / (scrollDuration / 15);

            const scrollInterval = setInterval(function() {
                if (window.scrollY !== 0) {
                    window.scrollBy(0, scrollStep);
                } else {
                    clearInterval(scrollInterval);
                }
            }, 15);
        }

        // 添加键盘支持：按 Home 键返回顶部
        document.addEventListener('keydown', function(e) {
            if (e.key === 'Home') {
                e.preventDefault();
                scrollToTop();
            }
        });
    });

    // 确保只执行一次
    if (!window.eventHandlersBound) {
        window.eventHandlersBound = true;
        layui.use(['jquery', 'layer'], function(){
            var $ = layui.jquery;
            var layer = layui.layer;

            $(document).on('click', '#copy-btn', function(){
                var columnIndex = 3;
                var content = [];

                $('.layui-table tbody tr').each(function(){
                    var text = $(this).find('td').eq(columnIndex).text().trim();
                    content.push(text || '');
                });

                if (content.length === 0) {
                    layer.msg('没有可复制的内容！', {icon: 2});
                    return;
                }

                var textToCopy = content.join('\n');

                if (navigator.clipboard) {
                    navigator.clipboard.writeText(textToCopy)
                        .then(() => {
                            layer.msg('复制成功！', {icon: 1});
                        })
                        .catch(err => {
                            layer.msg('复制失败：' + err.message, {icon: 2});
                        });
                } else {
                    var $temp = $('<textarea>');
                    $('body').append($temp);
                    $temp.val(textToCopy).select();
                    document.execCommand('copy');
                    $temp.remove();
                    layer.msg('复制成功！', {icon: 1});
                }
            });
        });
    }
</script>
</body>
</html>